//!闭包（匿名函数）

#[test]
pub fn closure_demo() {
    //闭包
    let max = |num1: i32, num2: i32| {
        if num1 > num2 {
            return num1;
        }
        return num2;
    };

    println!("{}", max(3, 4));
}

/*闭包作为结构体属性,泛型*/
pub struct MyCacher<T>
where
    T: Fn(u32) -> u32, //闭包类型
{
    value: Option<u32>,
    calculate: T,
}
impl<T> MyCacher<T>
where
    T: Fn(u32) -> u32,
{
    pub fn new(calculate: T) -> MyCacher<T> {
        MyCacher {
            calculate,
            value: None,
        }
    }
    pub fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculate)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

#[test]
pub fn closure_demo2() {
    let mut cacher = MyCacher::new(|num: u32| num);
    let v = cacher.value(45);
    println!("{}", v);
    let v = cacher.value(12);
    println!("{}", v);
}
// 捕获变量

#[test]
pub fn demo3() {
    let num = 5;
    let plus_num = |x: i32| x + num;

    assert_eq!(10, plus_num(5));

    //不能取得一个num的可变借用因为闭包已经借用了它
    //let y = &mut num;
}
//如果我们让闭包离开作用域，我们可以
#[test]
pub fn demo4() {
    let mut num = 5;
    {
        let plus_num = |x: i32| x + num;
    } // plus_num goes out of scope, borrow of num ends

    let y = &mut num;
}

//如果你的闭包需要它，Rust会取得所有权并移动环境
#[test]
pub fn demo5() {
    let nums = vec![1, 2, 3];

    //闭包会取得nums的所有权，nums失效
    let takes_nums = || nums;

    // println!("{:?}", nums);
    println!("{:?}", takes_nums());
}

// move闭包,可以使用move关键字强制使我们的闭包取得它环境的所有权
#[test]
pub fn demo6() {
    let num = 5;
    //5实现了Copy，所以owns_num取得一个5的拷贝的所有权
    let owns_num = move |x: i32| x + num;
    println!("{}", num);

    //不用move关键字
    let mut num = 5;
    let mut s: String = String::from("abc");

    //不用move关键字，闭包取得了一个num的可变引用
    let mut add_num =  |x: i32| {
        num += x;
        s.push_str("x");
    };



    add_num(5);
    println!("{}", s);
    assert_eq!(10, num);

    //如果我们加上move修饰闭包，会发生些不同：
    //这次我们没有获取到外部的num的可变借用，我们实际上是把 num move 进了闭包。
    // 因为 num 具有 Copy 属性，因此发生 move 之后，以前的变量生命周期并未结束，还可以继续在 assert_eq! 中使用。
    // 我们打印的变量和闭包内的变量是独立的两个变量。如果我们捕获的环境变量不是 Copy 的，那么外部环境变量被 move 进闭包后，
    // 它就不能继续在原先的函数中使用了，只能在闭包内使用。
    let mut num = 5;
    {
        let mut add_num = move |x: i32| num += x;
        add_num(5);
    }
    assert_eq!(5, num);
}
